﻿using System.Collections.Generic;
using Fadd;

namespace System.Collections.Specialized
{
	/// <summary>
	/// An implementation of the .Net NameValueCollection which is used extensively in fadd and is missing in the silverlight framework
	/// </summary>
	/// <remarks>
	/// ll values or lists retrieved from the class are strictly read-only copies of the actual values. To make changes use the explicit methods like
	/// <see cref="Add"/>, <see cref="Set"/> or <see cref="Remove"/>
	/// </remarks>
	public class NameValueCollection : IEnumerable<string>, IEnumerable<KeyValuePair<string, string>>
	{
		#region Container class for holding concated strings and their values

		/// <summary>
		/// Container class holding a string as well as the values inputted to the string
		/// </summary>
		internal class KeyedValue
		{
			private string _value;
			private readonly List<string> _values = new List<string>();

			/// <summary>
			/// Gets or sets the value held
			/// </summary>
			public string Value
			{
				get { return _value; }
				set
				{
					_value = value;
					_values.Clear();
					_values.Add(value);
				}
			}
			
			/// <summary>
			///  Returns the values held
			/// </summary>
			public IList<string> Values
			{
				get { return _values.AsReadOnly(); }
			}

			/// <summary>
			/// Adds a value to the current value
			/// </summary>
			/// <param name="value">The value to add, will be appended to any current string separated by a comma</param>
			public void AddValue(string value)
			{
				if(!string.IsNullOrEmpty(_value))
				{
					_value += "," + value;
					_values.Add(value);
				}
					Value = value;
			}
		}

		#endregion

		private readonly Dictionary<string, KeyedValue> _values = new Dictionary<string, KeyedValue>();

		/// <summary>
		/// Removes all values held by the collection
		/// </summary>
		public void Clear()
		{
			_values.Clear();
		}

		/// <summary>
		/// Returns the number of keys held by the <see cref="NameValueCollection"/>
		/// </summary>
		public int Count
		{
			get { return _values.Count; }
		}

		/// <summary>
		/// Returns the value with the given key or null
		/// </summary>
		/// <param name="index">Key to return value for</param>
		/// <returns>A string value or null</returns>
		public string this[string index]
		{
			get
			{	
				Check.Require(index, "index");
				KeyedValue output;
				return _values.TryGetValue(index, out output) ? output.Value : null;
			}
		}

		/// <summary>
		/// Gets all values mapped to the key
		/// </summary>
		/// <param name="key"></param>
		/// <returns>A list of values, may be empty if no list is found</returns>
		public IList<string> GetValues(string key)
		{
			Check.Require(key, "key");

			KeyedValue container;
			if(!_values.TryGetValue(key, out container))
				return new List<string>();

			return container.Values;
		}

		/// <summary>
		/// Adds a value to the key (will be appended to any existing value)
		/// </summary>
		/// <param name="key">The key to add the value to</param>
		/// <param name="value">The value to add</param>
		public void Add(string key, string value)
		{
			Check.Require(key, "key");
			
			KeyedValue container;
			if(_values.TryGetValue(key, out container))
				container.AddValue(value);
			else
				_values.Add(key, new KeyedValue{Value = value});
		}

		/// <summary>
		/// Overwrites any value bound to the key with the value specified
		/// </summary>
		/// <param name="key">The key to set a value for</param>
		/// <param name="value">The value to set</param>
		public void Set(string key, string value)
		{
			Check.Require(key, "key");

			KeyedValue container;
			if (_values.TryGetValue(key, out container))
				container.Value = value;
			else
				_values.Add(key, new KeyedValue { Value = value });
		}

		/// <summary>
		/// Removes any values mapped to the key
		/// </summary>
		/// <param name="key">The key to remove values for</param>
		/// <returns>True if the key was found and values could be removed</returns>
		public bool Remove(string key)
		{
			return _values.Remove(key);
		}

		/// <summary>
		/// Gets an iterator with a <see cref="KeyValuePair{TKey,TValue}"/> holding the key and value
		/// </summary>
		/// <returns></returns>
		IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
		{
			foreach (KeyValuePair<string, KeyedValue> pair in _values)
				yield return new KeyValuePair<string, string>(pair.Key, pair.Value.Value);
		}

		/// <summary>
		/// Returns an enumerable over all keys in the collection
		/// </summary>
		/// <returns></returns>
		IEnumerator<string> IEnumerable<string>.GetEnumerator()
		{
			return _values.Keys.GetEnumerator();
		}

		/// <summary>
		/// Returns an enumerator holding a string value
		/// </summary>
		public IEnumerator GetEnumerator()
		{
			return ((IEnumerable<string>)this).GetEnumerator();
		}
	}
}
